﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Schema;

namespace CompletionData
{
    public static class MSBuildXmlSchema
    {
        public static List<XmlSchemaElement> GetDescendantElement(XmlSchema schema, XmlSchemaElement elem, string[] keys)
        {
            if (schema == null)
                throw new ArgumentNullException();
            if (elem == null)
                throw new ArgumentNullException();
            if (keys == null || keys.Length <= 0)
                throw new ArgumentNullException();

            XmlSchemaElement descendant = FindChildElement(schema, elem, keys);
            List<XmlSchemaElement> result = new List<XmlSchemaElement>();
            if (descendant == null)
                return result;
            return GetChildElements(schema, descendant);
        }

        private static List<XmlSchemaElement> GetChildElements(XmlSchema schema, XmlSchemaElement elem)
        {
            if (schema == null)
                throw new ArgumentNullException();
            if (elem == null)
                throw new ArgumentNullException();

            List<XmlSchemaElement> result = new List<XmlSchemaElement>();
            XmlSchemaComplexType complexType = elem.ElementSchemaType as XmlSchemaComplexType;
            if (complexType == null)
                return result;
            if (complexType.ContentTypeParticle == null)
                return result;

            if (complexType.ContentTypeParticle is XmlSchemaElement)
                result.Add(complexType.ContentTypeParticle as XmlSchemaElement);
            else
            {
                List<XmlSchemaElement> children = new List<XmlSchemaElement>();
                if (complexType.ContentTypeParticle is XmlSchemaChoice)
                    GetXmlSchemaElement(schema, complexType.ContentTypeParticle as XmlSchemaChoice,children);
                else if (complexType.ContentTypeParticle is XmlSchemaSequence)
                    GetXmlSchemaElement(schema, complexType.ContentTypeParticle as XmlSchemaSequence,children);
                if (children != null && children.Count > 0)
                    result.AddRange(children);
            }
            
            result.Sort((a,b)=>string.Compare(a.Name,b.Name));
            return result;
        }

        private static XmlSchemaElement FindChildElement(XmlSchema schema, XmlSchemaElement elem, string[] keys)
        {
            XmlSchemaElement foundElem = elem;
            foreach (string key in keys)
            {
                foundElem = FindChildElement(schema, foundElem, key);
                if (foundElem == null)
                    break;
            }

            return foundElem;
        }

        private static XmlSchemaElement FindChildElement(XmlSchema schema, XmlSchemaElement elem, string targetName)
        {
            if (schema == null)
                throw new ArgumentNullException();
            if (elem == null)
                throw new ArgumentNullException();
            if (string.IsNullOrWhiteSpace(targetName))
                throw new ArgumentNullException();

            XmlSchemaElement result = null;
            if (string.Equals(elem.Name, targetName, StringComparison.CurrentCultureIgnoreCase))
                return elem;

            XmlSchemaComplexType complexType = elem.ElementSchemaType as XmlSchemaComplexType;
            if (complexType == null)
                return result;
            if (complexType.ContentTypeParticle == null)
                return result;

            if (complexType.ContentTypeParticle is XmlSchemaElement)
            {
                XmlSchemaElement xse = complexType.ContentTypeParticle as XmlSchemaElement;
                if (string.Equals(xse.Name, targetName, StringComparison.CurrentCultureIgnoreCase))
                    result = xse;
            }
            else
            {
                List<XmlSchemaElement> children = new List<XmlSchemaElement>();
                if (complexType.ContentTypeParticle is XmlSchemaChoice)
                    GetXmlSchemaElement(schema, complexType.ContentTypeParticle as XmlSchemaChoice, children);
                else if (complexType.ContentTypeParticle is XmlSchemaSequence)
                    GetXmlSchemaElement(schema, complexType.ContentTypeParticle as XmlSchemaSequence, children);
                if (children != null && children.Count > 0)
                    result = children.Find(c => string.Equals(c.Name, targetName, StringComparison.CurrentCultureIgnoreCase));
            }

            return result;
        }

        private static List<XmlSchemaElement> FindElementBySubstitutionGroup(XmlSchema schema, XmlSchemaElement elem)
        {
            if (schema == null)
                throw new ArgumentNullException();
            if (elem == null)
                throw new ArgumentNullException();

            List<XmlSchemaElement> result = new List<XmlSchemaElement>();
            if (elem.RefName == null || string.IsNullOrWhiteSpace(elem.RefName.Name))
                return result;
            foreach (XmlSchemaObject xso in schema.Elements.Values)
            {
                XmlSchemaElement xse = xso as XmlSchemaElement;
                if (elem.RefName.Name == xse.SubstitutionGroup.Name && elem.RefName.Namespace == xse.SubstitutionGroup.Namespace)
                    result.Add(xse);
            }

            return result;
        }

        private static void GetXmlSchemaElement(XmlSchema schema, XmlSchemaGroupBase particle, List<XmlSchemaElement> foundElements)
        {
            if (schema == null)
                throw new ArgumentNullException();
            if (particle == null)
                throw new ArgumentNullException();
            if (foundElements == null)
                foundElements = new List<XmlSchemaElement>();

            if (particle.Items == null || particle.Items.Count <= 0)
                return;
            foreach (XmlSchemaObject xso in particle.Items)
            {
                if (xso is XmlSchemaChoice)
                    GetXmlSchemaElement(schema, xso as XmlSchemaChoice,foundElements);
                else if (xso is XmlSchemaSequence)
                    GetXmlSchemaElement(schema, xso as XmlSchemaSequence, foundElements);
                else if (xso is XmlSchemaElement)
                {
                    XmlSchemaElement xse = xso as XmlSchemaElement;
                    if (xse.RefName != null && !string.IsNullOrWhiteSpace(xse.RefName.Name))
                    {
                        List<XmlSchemaElement> refElements = FindElementBySubstitutionGroup(schema, xse);
                        List<XmlSchemaElement> addElems = refElements.FindAll(elem => !foundElements.Exists(f => elem.Name == f.Name));
                        foundElements.AddRange(addElems);
                    }
                    else
                    {
                        if (!foundElements.Exists(elem => elem.Name == xse.Name))
                            foundElements.Add(xse);
                    }
                }
                else
                    continue;
            }
        }

    }
}
